#include "StdAfx.h"
#include "Note.h"

#include <SFML/System/Randomizer.hpp>
#include <SFML/Audio/SoundFile.hpp>
#include <math.h>

static 	sf::Randomizer randomizer;
double variableIntensityOffset(int intensity, int intensityVariation)
{
	double halfRange = intensity*(intensityVariation/100.0)/2;
	double d = randomizer.Random((float)-halfRange, (float)halfRange);
	return d;
}

void (*waveLoopCb)(short* sample, int idx, int intensity, int intensityVariation, int phase, int phasePos, double phaseLength);

void waveLoop(double frequency, int rate, int intensity, int intensityVariation, int phaseCount, short* sample, int sampleLength, 
			  int* startIndex, int* phaseIndex, void(*waveLoopCb)(short*,int,int,int, int,int,double))
{
	double samplesPerPulse = ((double)rate)/frequency;
	double phaseLength = samplesPerPulse/phaseCount;
	int sampleIdx=0;
	int phase = *phaseIndex;
	int counter = *startIndex;
	int i = *startIndex;
	while(sampleIdx<sampleLength)
	{
		for(; i<samplesPerPulse && sampleIdx<sampleLength; i++, counter++, sampleIdx++)
		{
			if(counter>=phaseLength)
			{
				phase++;
				counter = 0;
				if(phase>=phaseCount)
					phase = 0;
			}
			waveLoopCb(sample, sampleIdx, intensity, intensityVariation, phase, counter, phaseLength);
		}
		if(sampleIdx==sampleLength)
			break;
		i=0;
	}
	*startIndex=i;
	*phaseIndex = phase;
}

void squareImpl(short* sample, int sampleIdx, int intensity, int intensityVariation, int phase, int phasePos, double phaseLength)
{
	double offset = variableIntensityOffset(intensity, intensityVariation);
	sample[sampleIdx] += offset;
	switch(phase) {
		case 0:
			sample[sampleIdx] += intensity/2;
			break;
		case 1:
			sample[sampleIdx] -= intensity/2;
			break;
	}
}
void square(int frequency, int rate, int intensity, int intensityVariation, short* sample, int sampleLength, int* startIndex, int* phaseIndex)
{
	waveLoop(frequency, rate, intensity, intensityVariation, 2, sample, sampleLength, startIndex, phaseIndex, squareImpl);
}

void triangularImpl(short* sample, int sampleIdx, int intensity, int intensityVariation, int phase, int phasePos, double phaseLength)
{
	double offset = variableIntensityOffset(intensity, intensityVariation);
	sample[sampleIdx] += offset;
	double strength = ((double)phasePos / phaseLength) * ((double)intensity / 2.0 );
	int roundedStrength = (int)strength;
	int add = 0;
	switch(phase) {
		case 0:
			add = roundedStrength;
			break;
		case 1:
			add = (intensity/2)-roundedStrength;
			break;
		case 2:
			add -= roundedStrength;
			break;
		case 3:
			add = (-intensity/2)+roundedStrength;
			break;
	}
	sample[sampleIdx] += add;
}
/*
void triangular(int frequency, int rate, int intensity, short* sample, int sampleLength, int* startIndex, int* phaseIndex)
{
	waveLoop(frequency, rate, intensity, 4, sample, sampleLength, startIndex, phaseIndex, triangularImpl);	
}
*/
static double pi2oN = 0;
static int multConstant = (1 << (16 - 1)) - 1;
void sineImpl(short* sample, int sampleIdx, int intensity, int intensityVariation, int phase, int phasePos, double phaseLength)
{
	double offset = variableIntensityOffset(intensity, intensityVariation);
	sample[sampleIdx] += offset;
	double dval = sin( pi2oN*phasePos );
	double intensityMult = (double)intensity/32000.0;
	short sval = (short)(dval*multConstant*intensityMult);
	sample[sampleIdx] += sval;
}
/*
void sine(int frequency, int rate, int intensity, short* sample, int sampleLength, int* startIndex, int* phaseIndex)
{
	pi2oN = 8.0 * atan(1.0) / (rate/frequency);
	waveLoop(frequency, rate, intensity, 1, sample, sampleLength, startIndex, phaseIndex, sineImpl);
}
*/

void sawtoothImpl(short* sample, int sampleIdx, int intensity, int intensityVariation, int phase, int phasePos, double phaseLength)
{
	double offset = variableIntensityOffset(intensity, intensityVariation);
	sample[sampleIdx] += offset;
	double strength = ((double)phasePos / phaseLength) * ((double)intensity / 2.0 );
	int roundedStrength = (int)strength;
	int add = 0;
	switch(phase) {
		case 0:
			add = roundedStrength;
			break;
		case 1:
			add = (-intensity/2)+roundedStrength;
			break;
	}
	sample[sampleIdx] += add;
}

Note::Note(void)
{
	NbSamples = 0;
	Samples = NULL;
}

Note::~Note(void)
{
}



void Note::InitSample(int NbSamples, int Rate)
{
	if(Samples!=NULL)
	{
		delete Samples;
	}

	this->NbSamples = NbSamples;
	Samples = new short[NbSamples];
	for(int i=0; i<NbSamples; i++)
	{
		Samples[i] = 0;
	}
	this->Rate = Rate;
}

int Note::GetSampleLength()
{
	return NbSamples;
}

short* Note::GetData()
{
	return Samples;
}

void Note::GetSFMLData(sf::SoundStream::Chunk& Data)
{
	Data.NbSamples = NbSamples;
	Data.Samples = Samples;
}

void Note::SawTooth(int Frequency, int Range)
{
	int SamplesPerPulse = Rate/Frequency;
	int HalfPart = SamplesPerPulse/2;
	int Phase = 0;
	int Counter = 0;
	for(int i=0; i<NbSamples; i++, Counter++)
	{
		if(Counter==HalfPart)
		{
			Phase++;
			Counter = 0;
		}
		if(Phase==2)
		{
			Phase = 0;
		}
		double Strength = ((double)Counter / (double)HalfPart) * ((double)Range / 2.0 );
		int RoundedStrength = (int)Strength;
		int Add = 0;
		switch(Phase) {
			case 0:
				Add = RoundedStrength;
				break;
			case 1:
				Add = (-Range/2)+RoundedStrength;
				break;
		}
		Samples[i] += Add;
	}
}

void Note::Triangular(int Frequency, int Range)
{
	int SamplesPerPulse = Rate/Frequency;
	int FourthPart = SamplesPerPulse/4;
	int Phase = 0;
	int Counter = 0;
	for(int i=0; i<NbSamples; i++, Counter++)
	{
		if(Counter==FourthPart)
		{
			Phase++;
			Counter = 0;
		}
		if(Phase==4)
		{
			Phase = 0;
		}
		double Strength = ((double)Counter / (double)FourthPart) * ((double)Range / 2.0 );
		int RoundedStrength = (int)Strength;
		int Add = 0;
		switch(Phase) {
			case 0:
				Add = RoundedStrength;
				break;
			case 1:
				Add = (Range/2)-RoundedStrength;
				break;
			case 2:
				Add -= RoundedStrength;
				break;
			case 3:
				Add = (-Range/2)+RoundedStrength;
				break;
		}
		Samples[i] += Add;
	}
}

void Note::Square(int Frequency, int Range) 
{
	int startIdx = 0;
	int phaseIdx = 0;
	square(Frequency, Rate, Range, 0, Samples, NbSamples, &startIdx, &phaseIdx);
}


void Note::TriSquare(int Frequency, int Range) 
{
	int SamplesPerPulse = Rate/Frequency;
	int SixthPart = SamplesPerPulse/6;
	int Phase = 0;
	int Counter = 0;
	for(int i=0; i<NbSamples; i++, Counter++)
	{
		if(Counter==SixthPart)
		{
			Phase++;
			Counter = 0;
		}
		if(Phase==6)
		{
			Phase = 0;
		}
		switch(Phase) {
			case 0:
				break;
			case 1:
			case 2:
				Samples[i] += Range/2;
				break;
			case 3:
			case 4:
				Samples[i] -= Range/2;
				break;
			case 5:
				break;
		}
	}
}

void Note::QuadSquare(int Frequency, int Range) 
{
	int SamplesPerPulse = Rate/Frequency;
	int FourthPart = SamplesPerPulse/4;
	int Phase = 0;
	int Counter = 0;
	for(int i=0; i<NbSamples; i++, Counter++)
	{
		if(Counter==FourthPart)
		{
			Phase++;
			Counter = 0;
		}
		if(Phase==4)
		{
			Phase = 0;
		}
		switch(Phase) {
			case 0:
				Samples[i] += Range/2;
				break;
			case 1:
				break;
			case 2:
				Samples[i] -= Range/2;
				break;
			case 3:
				break;
		}
	}
}

void Note::WhiteNoise(int Range)
{
	int min = 0-(Range/2);
	int max = Range/2;
	for(int i=0; i<NbSamples; i++) 
	{
		int next = randomizer.Random(min, max);
		Samples[i] += next;
	}
}

void Note::SetNote(int NbSamples, int Rate, int frequency, 
				int WhiteNoiseRange, int TriangularRange, int SawToothRange)
{
	InitSample(NbSamples, Rate);
	if(WhiteNoiseRange) WhiteNoise(WhiteNoiseRange);
	//if(SquareWaveRange) Square(frequency, SquareWaveRange);
	//if(TriangularRange) Triangular(frequency, TriangularRange);
	if(SawToothRange) SawTooth(frequency, SawToothRange);
}

void Note::RandomNote(bool save)
{
	int rate = 48000;
	int frequency = randomizer.Random(55, 110);
	int whiteNoiseRange = randomizer.Random(2500, 7500);
	int squareWaveRange = randomizer.Random(0, 5000);
	int triangularRange = randomizer.Random(0, 20000);
	int sawToothRange = randomizer.Random(0, 5000);
	SetNote(rate*8, rate, frequency, whiteNoiseRange, triangularRange, sawToothRange);
	if(save)
	{
		char* filename = new char[255];
		sprintf(filename, "sound\\carrier\\%d_%d_%d_%d_%d.wav", frequency, whiteNoiseRange, squareWaveRange, triangularRange, sawToothRange);
		std::cout << filename << std::endl;
		sf::priv::SoundFile* soundFile = sf::priv::SoundFile::CreateWrite(filename, 1, rate);
		soundFile->Write(GetData(), GetSampleLength());
		delete soundFile;
	}
}

void Note::CrossFade(Note* oldNote, int oldNoteIndex, int length)
{
	if(oldNote && oldNote->Samples)
	{
		for (int i = 0; i < length; ++i)
		{
			Samples[i] = (Samples[i]*(i/(double)length)) +
				(oldNote->Samples[i+oldNoteIndex]*((length-i)/(double)length));        
		}
	}
}

void Note::AddComponents(WaveComponent* components, int componentCount)
{
	for(int i=0; i<componentCount; i++)
	{
		AddComponent(components++);
	}
}

void Note::AddComponent(WaveComponent* component)
{
	if(component->intensity)
	{
		int	startIndex = component->waveIndex;
		int	phaseIndex = component->phaseIndex;
		int phaseCount = 0;
		void(*waveLoopCb)(short*,int,int,int,int,int,double) = NULL;
		switch(component->waveForm)
		{
		case WaveForm::Sawtooth:
			phaseCount = 2;
			waveLoopCb = sawtoothImpl;
			break;
		case WaveForm::Sine:
			phaseCount = 1;
			waveLoopCb = sineImpl;
			pi2oN = 8.0 * atan(1.0) / ((double)Rate/component->frequencyStart);
			break;
		case WaveForm::Square:
			phaseCount = 2;
			waveLoopCb = squareImpl;
			break;
		case WaveForm::Triangular:
			phaseCount = 4;
			waveLoopCb = triangularImpl;
			break;
			/*
		case WaveForm::Sawtooth:
			break;
		case WaveForm::Sine:
			startIndex = component->waveIndex;
			phaseIndex = component->phaseIndex;
			sine(component->frequencyStart, Rate, component->intensity, Samples, NbSamples, 
				&(component->waveIndex), &(component->phaseIndex));
			component->waveIndex = startIndex;
			component->phaseIndex = phaseIndex;
			break;
		case WaveForm::Square:
			square(component->frequencyStart, Rate, component->intensity, Samples, NbSamples, 
				&(component->waveIndex), &(component->phaseIndex));
			break;
		case WaveForm::Triangular:
			triangular(component->frequencyStart, Rate, component->intensity, Samples, NbSamples, 
				&(component->waveIndex), &(component->phaseIndex));
			break;
			*/
		}
		waveLoop(component->frequencyStart, this->Rate, component->intensity, 
			component->intensityVariation, phaseCount, Samples,
			NbSamples, &startIndex, &phaseIndex, waveLoopCb);
		component->waveIndex = startIndex;
		component->phaseIndex = phaseIndex;
	}
}

void Note::Smooth(int threshold)
{
	Smooth(Samples, NbSamples, threshold);
}

static short lastSample = 0;
void Note::Smooth(short* sample, int sampleLength, int threshold)
{
	int halfThres = threshold/2;
	for(int i=0; i<sampleLength; i++)
	{
		if(abs(lastSample-sample[i])>halfThres)
		{
			sample[i] = (sample[i]+halfThres+lastSample)/2;
		}
		lastSample = sample[i];
	}
}